﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Repositories.SearchExtensions;
using ControleRepresentante.Logic.Statistics;
using NHibernate;
using NHibernate.Criterion;
using Attribute = ControleRepresentante.Logic.Models.Attribute;

namespace ControleRepresentante.Logic.Repositories
{
    public interface IFabricaRepository : IUsuariosHolderRepository<Fabrica, StatisticsBase>
    {
        int GetCount();
        IEnumerable<Fabrica> GetModels(string nome, string cidade, string bairro, Dictionary<Attribute, string> attributeMap, ISearchExtension[] searchExtensions, out StatisticsBase statistics, int page = -1);
        IEnumerable<Fabrica> GetJolitexFabricas();
    }

    public enum FabricaErrors
    {
        DuplicatedNome,
        RepresentanteMissmatchError
    }

    public class FabricaRepository : UsuariosHolderRepository<Fabrica, StatisticsBase, FabricaErrors>, IFabricaRepository
    {
        public FabricaRepository(IRepositoryConfiguration repositoryConfiguration, ISession session) 
            : base(repositoryConfiguration, session)
        {
        }

        protected override void SetProperties(IEnumerable<Fabrica> fabricas)
        {
            base.SetProperties(fabricas);
            if (fabricas.Any())
            {
                IEnumerable<Contato> contatos = GetContatos(fabricas, ContatoType.FaxComercial, ContatoType.TelefoneComercial, ContatoType.Email);

                foreach (Fabrica fabrica in fabricas)
                {
                    SetContato(fabrica, contatos, ContatoType.FaxComercial, (m, c) => m.ContatoFax = c);
                    SetContato(fabrica, contatos, ContatoType.TelefoneComercial, (m, c) => m.ContatoTelefone = c);
                    SetContato(fabrica, contatos, ContatoType.Email, (m, c) => m.ContatoEmail = c);
                }
            }
        }

        protected override StatisticsBase GetStatistics(int modelCount)
        {
            return new StatisticsBase(modelCount);
        }

        protected override IEnumerable<Expression<Func<Fabrica, bool>>> DoGetUniquenessExpressions(Fabrica model)
        {
            yield return x => x.Nome == model.Nome;
        }

        protected override FabricaErrors RepresentanteMissmatchError
        {
            get { return FabricaErrors.RepresentanteMissmatchError; }
        }

        protected override FabricaErrors DuplicatedError { get { return FabricaErrors.DuplicatedNome; } }

        public int GetCount()
        {
            return Session.QueryOver<Fabrica>()
                .Where(x => x.Representante.Id == GetRepresentanteId())
                .RowCount();
        }

        public IEnumerable<Fabrica> GetModels(string nome, string cidade, string bairro, Dictionary<Attribute, string> attributeMap, ISearchExtension[] searchExtensions, out StatisticsBase statistics, int page = -1)
        {
            const string TableAlias = "fa";
            ICriteria criteria = CreateCriteria(cidade, bairro, attributeMap, TableAlias);

            if (!String.IsNullOrEmpty(nome))
            {
                string likeNome = "%" + nome + "%";
                criteria = criteria.Add(Restrictions.InsensitiveLike(Projections.Property<Fabrica>(x => x.Nome), likeNome));
            }

            IEnumerable<Fabrica> fabricaFiltro = User.Filtros.OfType<Fabrica>();
            if (fabricaFiltro.Any())
            {
                criteria.Add(Restrictions.In(Projections.Property<Fabrica>(x => x.Id), fabricaFiltro.Select(y => y.Id).ToArray()));
            }

            if (searchExtensions != null)
            {
                foreach (ISearchExtension searchExtension in searchExtensions)
                {
                    criteria = searchExtension.Execute(criteria, TableAlias);
                }
            }

            criteria = criteria.AddOrder(Order.Asc(Projections.Property<Fabrica>(x => x.Nome)));

            int modelCount;
            IEnumerable<Fabrica> fabricas = FuturePaging(criteria, page, out modelCount);
            SetProperties(fabricas);
            statistics = GetStatistics(modelCount);
            return fabricas;
        }

        public IEnumerable<Fabrica> GetJolitexFabricas()
        {
            DetachedCriteria detachedCriteria = DetachedCriteria.For<FabricaJolitex>("fj")
                .SetProjection(Projections.Property<FabricaJolitex>(x => x.Id))
                .Add(Restrictions.Eq(Projections.Property<FabricaJolitex>(x => x.IsJolitex), true))
                .Add(Restrictions.EqProperty("fa.Id", Projections.Property<FabricaJolitex>(x => x.Id)));
            
            return Session.CreateCriteria<Fabrica>("fa")
                .Add(Restrictions.Eq(Projections.Property<Fabrica>(x => x.Representante.Id), GetRepresentanteId()))
                .Add(Subqueries.Exists(detachedCriteria))
                .Future<Fabrica>();
        }

        protected override IQueryOver<Fabrica, Fabrica> DoGetModelsQueryOver(IQueryOver<Fabrica, Fabrica> queryOver)
        {
            return queryOver.OrderBy(x => x.Nome).Asc;
        }

        public override Dictionary<Guid, string> GetModelNames()
        {
            return Session.CreateSQLQuery(@"
                SELECT UsuariosHolderId, Nome
                FROM Fabrica
                INNER JOIN AttributeHolder ON UsuariosHolderId = AttributeHolder.Id
                WHERE RepresentanteId = :RepresentanteId
                ORDER BY Nome")
                .AddScalar("UsuariosHolderId", NHibernateUtil.Guid)
                .AddScalar("Nome", NHibernateUtil.String)
                .SetParameter("RepresentanteId", GetRepresentanteId())
                .List()
                .Cast<object[]>()
                .ToDictionary(x => (Guid)x[0], x => (string)x[1]);
        }

        public override Dictionary<Guid, string> GetModelNames(string search)
        {
            return Session.CreateSQLQuery(@"
                SELECT UsuariosHolderId, Nome
                FROM Fabrica
                INNER JOIN AttributeHolder ON UsuariosHolderId = AttributeHolder.Id
                WHERE RepresentanteId = :RepresentanteId AND Nome LIKE :Search
                ORDER BY Nome")
                .AddScalar("UsuariosHolderId", NHibernateUtil.Guid)
                .AddScalar("Nome", NHibernateUtil.String)
                .SetParameter("RepresentanteId", GetRepresentanteId())
                .SetParameter("Search", "%" + search + "%")
                .List()
                .Cast<object[]>()
                .ToDictionary(x => (Guid)x[0], x => (string)x[1]);
        }
    }
}